package d1_midterm_examination.demo04;

import java.util.Arrays;
import java.util.Objects;

/**
 * 需求: 如下
 * 1、MyArrayList需要支持泛型，内部使用数组作为容器。
 * 2、在MyArrayList中开发add方法，用于添加数据的，需要遵循ArrayList的扩容机制（自行设计代码，不需要与ArrayList的源代码一样，思想一致即可）
 * 3、在MyArrayList中开发根据索引查询数据的get方法。
 * 4、在MyArrayList中开发根据索引删除数据的remove方法。
 * 5、在MyArrayList中开发一个获取集合大小的size ()方法。
 * 6、能够在MyArrayList集合中开发一个forEach方法，这个方法支持使用Lambda进行遍历，至于函数式接口叫什么名称无所谓。
 * 7、编写测试用例对自己编写的MyArrayList集合进行功能正确性测试。
 *
 * @param <E>
 */
public class MyArrayList<E> {
    //1、定义一个数组,用于存储数据
    private Object[] elementData = {};
    //2、定义一个变量,用于记住元素的个数,也是存储数据的下一个位置
    private int size;
    //3、定义一个变量记住初始化容量
    private int DEFAULT_CAPACITY = 10;

    //9、开发一个获取集合大小的size()方法
    public int size(){
        return size;
    }

    //8、写一个toString()方法用于展示集合


    @Override
    public String toString() {
        //1.首先创建一个StringBuilder容器用于拼接
        StringBuilder sb = new StringBuilder();
        //2.向打印一个"["
        sb.append("[");
        for (int i = 0; i < size; i++) {
            E e = (E) elementData[i];//按照官方的写法,节省性能
            sb.append(e).append(i == size -1 ? "": ",");//最后就不要用括号, 这样子很浪费性能
        }
        //3.最后在拼一个"]"
        sb.append("]");
        return sb.toString();//打成字符串进行返回
    }

    //7、开发提个forEach方法, 这个方法支持使用Lambda进行遍历, 至于函数是接口叫什么名称无所谓
    public void forEach(MyConsumer<E> action){
        //1.首先做非空判断
        Objects.requireNonNull(action);
        //2.开始遍历
        for (int i = 0; i < size; i++) {
            action.accept((E) elementData[i]);//典型的对象回调方法,其实forEach是不止到,要做什么事的,要创建匿名内部类的时候才知道
        }
    }

    //6、开发根据索引删除数据的remove方法 ---> 返回被删除的数据
    public E remove(int index) {
        //1.做索引月越界判断
        checkIndex(index);
        //提前将数据获取到
        E e = (E) elementData[index];
        //2.设置一个标志,删除有几位,删除的是不是最后一位
        int removeFlag = size - index - 1;
        //3.根据标志进行删除-->操作
        if (removeFlag != 0) {
            //4.到这里就是需要移位操作
            /*arraycopy(Object src,  int  srcPos,
            Object dest, int destPos,
            int length);
             参数解释:
                    src - 源数组。
                    srcPos - 源数组中的起始位置。
                    dest - 目标数组。
                    destPos - 目标数据中的起始位置。
                    length - 要复制的数组元素的数量


            */

            System.arraycopy(elementData, index + 1, elementData, index, removeFlag);
        }
        //5.到这里就是要删除的是最后一位----->需要size减1,且将删除位置置成null
        elementData[--size] = null;//就是将数组中是size减1之后的位置,置成null
        //6.最后将数据进行返回
        return e;
    }

    //5、开发根据索引查数据的get方法---->是返回获取到的数据数据
    public E get(int index) {
        //1.首先要做越界判断------------>很多地方需要做索引越界判断
        checkIndex(index);
        //能执行到这一步就证明是,并没有越界
        //2.直接根据索引从数组中获取数据
        return (E) elementData[index];
    }

    private void checkIndex(int index) {
        //1.判断是越界,如果越界就直接抛异常
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException(index + "out of max length" + size);
        }
    }


    //4、实现第一个功能, 添加数据
    public boolean add(E e) {
        //1.判断是否需要扩容
        if (size == elementData.length) {
            grow();//注意是不考虑一次添加多个数据的情况,过于复杂
        }
        //2.将数据存到数组中去,, 并且size加1
        elementData[size++] = e;//这个是将值赋给size索引所在的位置, 然后size在+1

        return true;
    }


    private void grow() {
        //1.第一次添加数据的情况
        if (size == 0) {
            //2.创建一个数组的容量,是默认容量
            elementData = new Object[DEFAULT_CAPACITY];
        } else {
            //3.不是第一次创建数据,扩容成原来的1.5倍
            //                          需要复制的数组             新数组的长度              右移一位,就是减半
            elementData = Arrays.copyOf(elementData, elementData.length + elementData.length >> 1);
        }
    }



}
